package org.sean.framework.util;

import cn.hutool.core.codec.Base64;
import cn.hutool.core.util.HexUtil;
import lombok.extern.slf4j.Slf4j;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.spec.DESedeKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;

/**
 * @author Sean.xie
 */
@Slf4j
public class CipherUtil {

    private CipherUtil() {
    }

    /**
     * AES 加密
     *
     * @param key  秘钥
     * @param data 数据
     * @return 加密数据
     */
    public static String encryptAESWithBase64(String key, String data) {
        try {
            return Base64.encode(encryptAES(key.getBytes(), data.getBytes()));
        } catch (Exception e) {
            log.warn(e.getMessage(), e);
            return null;
        }
    }

    /**
     * AES 加密
     *
     * @param key  秘钥
     * @param data 数据
     * @return 加密数据
     */
    public static String encryptAESWithHex(String key, String data) {
        try {
            return HexUtil.encodeHexStr(encryptAES(key.getBytes(), data.getBytes()));
        } catch (Exception e) {
            log.warn(e.getMessage(), e);
            return null;
        }
    }

    /**
     * DES 加密
     *
     * @param key  秘钥
     * @param data 数据
     * @return 加密数据
     */
    public static String encryptDESWithBase64(String key, String data) {
        try {
            return Base64.encode(encryptDES(key.getBytes(), data.getBytes()));
        } catch (Exception e) {
            log.warn(e.getMessage(), e);
            return null;
        }
    }

    /**
     * DESede 加密
     *
     * @param key  秘钥
     * @param data 数据
     * @return 加密数据
     */
    public static String encryptDES3WithBase64(String key, String data) {
        try {
            return Base64.encode(encryptDESede(key.getBytes(), data.getBytes()));
        } catch (Exception e) {
            log.warn(e.getMessage(), e);
            return null;
        }
    }

    /**
     * AES 解密
     *
     * @param key  秘钥
     * @param data 数据
     * @return 解密数据
     */
    public static String decryptWithBase64(String key, String data) {
        try {
            return new String(decrypt(key.getBytes(), Base64.decode(data)));
        } catch (Exception e) {
            log.warn(e.getMessage(), e);
            return null;
        }
    }

    /**
     * AES 解密
     *
     * @param key  秘钥
     * @param data 数据
     * @return 解密数据
     */
    public static String decryptAESWithBase64(String key, String data) {
        try {
            return new String(decrypt(key.getBytes(), Base64.decode(data)));
        } catch (Exception e) {
            log.warn(e.getMessage(), e);
            return null;
        }
    }

    /**
     * AES 解密
     *
     * @param key     秘钥
     * @param hexData 数据
     * @return 解密数据
     */
    public static String decryptAESWithHex(String key, String hexData) {
        try {
            return new String(decrypt(key.getBytes(), HexUtil.decodeHex(hexData)));
        } catch (Exception e) {
            log.warn(e.getMessage(), e);
            return null;
        }
    }

    /**
     * DES 解密
     *
     * @param key  秘钥
     * @param data 数据
     * @return 解密数据
     */
    public static String decryptDESWithBase64(String key, String data) {
        try {
            return new String(decryptDES(key.getBytes(), Base64.decode(data)));
        } catch (Exception e) {
            log.warn(e.getMessage(), e);
            return null;
        }
    }

    /**
     * DESede 解密
     *
     * @param key  秘钥
     * @param data 数据
     * @return 解密数据
     */
    public static String decryptDES3WithBase64(String key, String data) {
        try {
            return new String(decryptDESede(key.getBytes(), Base64.decode(data)));
        } catch (Exception e) {
            log.warn(e.getMessage(), e);
            return null;
        }
    }

    /**
     * AES 加密
     *
     * @param key  秘钥
     * @param data 数据
     * @return 加密数据
     * @throws Exception 异常
     */
    public static byte[] encryptAES(byte[] key, byte[] data) throws Exception {
        return encrypt(key, data, CipherType.AES);
    }

    /**
     * 加密
     *
     * @param key  秘钥
     * @param data 数据
     * @param type 加密类型
     * @return 加密数据
     * @throws Exception 异常
     */
    public static byte[] encrypt(byte[] key, byte[] data, CipherType type) throws Exception {
        return cipher(key, data, type, Cipher.ENCRYPT_MODE);
    }

    /**
     * AES 解密
     *
     * @param key  秘钥
     * @param data 数据
     * @return 解密数据
     * @throws Exception 异常
     */
    public static byte[] decrypt(byte[] key, byte[] data) throws Exception {
        return decrypt(key, data, CipherType.AES);
    }

    /**
     * 解密
     *
     * @param key  秘钥
     * @param data 数据
     * @param type 解密类型
     * @return 解密数据
     * @throws Exception 异常
     */
    public static byte[] decrypt(byte[] key, byte[] data, CipherType type) throws Exception {
        return cipher(key, data, type, Cipher.DECRYPT_MODE);
    }

    /**
     * DES 加密
     *
     * @param key  秘钥
     * @param data 数据
     * @return 加密数据
     * @throws Exception 异常
     */
    public static byte[] encryptDES(byte[] key, byte[] data) throws Exception {
        return encrypt(key, data, CipherType.DES);
    }

    /**
     * DES 解密
     *
     * @param key  秘钥
     * @param data 数据
     * @return 解密数据
     * @throws Exception 异常
     */
    public static byte[] decryptDES(byte[] key, byte[] data) throws Exception {
        return decrypt(key, data, CipherType.DES);
    }

    /**
     * DESede 加密
     *
     * @param key  秘钥
     * @param data 数据
     * @return 加密数据
     * @throws Exception 异常
     */
    public static byte[] encryptDESede(byte[] key, byte[] data) throws Exception {
        return encrypt(key, data, CipherType.DESEDE);
    }

    /**
     * DESede 解密
     *
     * @param key  秘钥
     * @param data 数据
     * @return 解密数据
     * @throws Exception 异常
     */
    public static byte[] decryptDESede(byte[] key, byte[] data) throws Exception {
        return decrypt(key, data, CipherType.DESEDE);
    }

    /**
     * 加/解密
     *
     * @param key  秘钥
     * @param data 数据
     * @param type 类型
     * @param mode 模式
     * @return 加/解密数据
     * @throws Exception 异常
     */
    private static byte[] cipher(byte[] key, byte[] data, CipherType type, int mode) throws Exception {
        if (key == null || key.length < 16) {
            throw new IllegalArgumentException("key length must lager than 16");
        }
        SecretKey secretKey;
        switch (type) {
            case DESEDE:
                if (key.length < 24) {
                    throw new IllegalArgumentException("key length must lager than 24");
                }
                secretKey = SecretKeyFactory.getInstance(type.name()).generateSecret(new DESedeKeySpec(key));
                break;
            case DES:
                secretKey = SecretKeyFactory.getInstance(type.name()).generateSecret(new DESKeySpec(key));
                break;
            case AES:
                secretKey = new SecretKeySpec(getAESSecretKey(key, type), type.name());
                break;
            default:
                throw new IllegalArgumentException();
        }
        Cipher cipher = Cipher.getInstance(type.getAlgorithm()); //Cipher完成加密或解密工作类
        cipher.init(mode, secretKey); //对Cipher初始化，解密模式
        return cipher.doFinal(data); //加密data
    }

    /**
     * 生成AES 秘钥
     *
     * @param key  秘钥
     * @param type 类型
     * @return 秘钥数据
     * @throws NoSuchAlgorithmException 异常
     */
    private static byte[] getAESSecretKey(byte[] key, CipherType type) throws NoSuchAlgorithmException {
        KeyGenerator kgen = KeyGenerator.getInstance(type.name());
        //128：密钥生成参数；JDK 最多只能处理128位
        SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
        random.setSeed(key);
        kgen.init(128, random);
        return kgen.generateKey().getEncoded();
    }

    private enum CipherType {
        /**
         * DES
         */
        DES("DES/ECB/PKCS5Padding"),
        /**
         * DES3
         */
        DESEDE("DESede/ECB/PKCS5Padding"),
        /**
         * AES
         */
        AES("AES/ECB/PKCS5Padding");

        private String algorithm;

        CipherType(String algorithm) {
            this.algorithm = algorithm;
        }

        public String getAlgorithm() {
            return algorithm;
        }
    }

}
